<?php
/**
 * Created by PhpStorm.
 * User: lhl
 * Date: 2020/10/16
 * Time: 17:05
 * Features: 后台权限
 */

namespace app\adminapiv3\model;


use app\common\service\Algorithm;
use app\common\service\StringHelper;
use think\Exception;

class ServerPermissionModel extends BaseModel
{
    //顶级权限名称
    const TOP_AUTH_NAME = '顶级权限';

    /**
     * 搜索器
     * @field name 权限名称
     */
    public function searchNameAttr($query, $value, $data){
        if(strlen($value)){
            $query->where('name','like',"%{$value}%");
        }
    }

    /**
     * 搜索器
     * @field is_menu 是否权限
     */
    public function searchIsMenuAttr($query, $value, $data){
        if(strlen($value)){
            $query->where('is_menu','=',$value);
        }
    }

    /**
     * 搜索器
     * @field route 权限路由
     */
    public function searchRouteAttr($query, $value, $data){
        if(strlen($value)){
            $query->where('route','like',"%{$value}%");
        }
    }

    /**
     * 搜索器
     * @field redirect_url 前台权限跳转地址
     */
    public function searchRedirectUrlAttr($query, $value, $data){
        if(strlen($value)){
            $query->where('redirect_url','like',"%{$value}%");
        }
    }

    /**
     * 搜索器
     * @field menu_name 前台权限标识
     */
    public function searchMenuNameAttr($query, $value, $data){
        if(strlen($value)){
            $query->where('menu_name','like',"%{$value}%");
        }
    }

    /**
     * 查询系统的顶级权限
     */
    public function getTopAuth(){
        return $this->where(['pid' => 0])->findOrEmpty()->toArray();
    }

    /**
     * 创建某系统顶级权限
     * @param string $system 所属系统
     */
    public function createTopAuth(){
        if (!$this->getTopAuth()) {
            $this->startTrans();
            try{
                $id = $this->insertGetId([
                    'name' => self::TOP_AUTH_NAME,
                    'is_menu' => 0,
                    'pid' => 0,
                    'level' => 0,
                    'root' => 0,
                    'route' => '',
                    'create_time' => time(),
                ]);
                if(!$id){
                    $this->rollback();
                    return false;
                }
                if(!$this->save(['path' => '0,'.$id],['id' => $id])){
                    $this->rollback();
                    return false;
                }
                $this->commit();
                return true;
            }catch (Exception $e){
                $this->rollback();
                return false;
            }
        }
        return true;
    }

    /**
     *  根据入参获取验证场景名称
     * @param array     $param 请求表单
     * @param string    $scene 原验证场景 add - 添加 modify - 修改
    */
    public function getValidateScene($param,$scene){
        if($param['is_menu']){
            return $scene . 'Menu';
        }else{
            return $scene;
        }
    }


    /**
     * 后台权限列表判断是否递归
     * @param array $search 搜索参数
     *
     * @return bool
    */
    public function isRecursive($search){
        //如果是搜索,则不递归排序,否则就需要递归排序
        if((isset($serach['name']) && strlen($serach['name'])) || (isset($serach['is_menu']) && strlen($serach['is_menu']))
            || (isset($serach['route']) && strlen($serach['route'])) || (isset($serach['redirect_url']) && strlen($serach['redirect_url']))
            || (isset($serach['menu_name']) && strlen($serach['menu_name']))){
            return false;
        }else{
            return true;
        }
    }

    /**
     * 查询后台权限列表数据
     * @param bool   $isIcon 是否添加前缀层级图标
     * @param bool   $isRoot 是否保留根节点
     * @param array  $search 搜索参数
     * @param bool   $isRecursive 是否递归整理树
     */
    public function getList($isIcon = false,$isRoot = false,$search = [],$isRecursive = true){
        if($isRecursive){
            $orderType = 1; //先按排序值排序,后递归整理树结构
        }else{
            $orderType = 0; //直接按path排序,无需递归排序
        }
        $auth = $this->getAuthData($orderType,$search);
        //整理树结构
        return $this->tree($auth,$isIcon,$isRoot,$isRecursive);
    }


    /**
     * 查询所有权限数据(包含顶级权限)
     * @param int    $orderType 0 - path字段 升序 1 - sorts字段 升序
     * @param array  $search 搜索参数
     */
    public function getAuthData($orderType = 0,$search = []){
        if($orderType){
            $auths = $this->order('sorts','asc');
        }else{
            $auths = $this->order('path','asc');
        }

        $list = $auths->withSearch(['name','is_menu','route','redirect_url','menu_name'],$search)->select()->toArray();
        $includeTop = false;
        foreach ($list as $item){
            if($item['pid'] == 0){
                $includeTop = true;
                break;
            }
        }
        if(!$includeTop){
            array_unshift($list,$this->getTopAuth());
        }
        return $list;
    }


    /**
     * 将排序好的权限数据整理成树形结构,并添加图标
     * @param array  $auth   已经根据排序值排好的权限数据
     * @param bool   $isIcon 是否添加前缀层级图标
     * @param bool   $isRoot 是否保留根节点
     * @param bool   $isRecursive 是否递归整理树
     */
    public function tree($auth,$isIcon = false,$isRoot = false,$isRecursive = true){
        if($isRecursive){
            //递归整理树结构
            $auth = Algorithm::tree($auth,0,2);
        }

        if($isIcon){
            //填充展示图标前缀
            foreach ($auth as $key =>$item){
                //再遍历一次判断该节点是不是同辈节点中按排序值的最后一个节点,如果是则为└，否则为├
                if($item['level'] > 1){
                    //找到同辈节点
                    $peer = array();
                    foreach ($auth as $k => $v){
                        if(($v['pid'] == $item['pid'])){
                            $peer[] = $v;
                        }
                    }
                    if(count($peer) <= 1){
                        //没有同辈节点,则为└
                        $auth[$key]['name_text'] = str_repeat('&ensp;',$item['level'] - 1).'└'.$item['name'];
                    }else{
                        //找到同辈节点中的最后一个元素
                        $end = end($peer);
                        if($item['id'] == $end['id']){
                            $auth[$key]['name_text'] = str_repeat('&ensp;',$item['level'] - 1).'└'.$item['name'];
                        }else{
                            $auth[$key]['name_text'] = str_repeat('&ensp;',$item['level'] - 1).'├'.$item['name'];
                        }
                    }
                }else{
                    $auth[$key]['name_text'] = $item['name'];
                }
            }
        }

        //去掉根节点
        if(!$isRoot){
            foreach ($auth as $key => $item){
                if($item['pid'] == 0)
                    unset($auth[$key]);
            }
        }
        //重新索引
        return array_merge($auth);
    }

    /**
     * 验证route参数格式,并进行转换
     * @param string $route 后台路由
     *
     * @return mixed
    */
    public function checkRouteParam($route){
        //"PlatformManage.accountAsBd.saBsCc.ServerPermission"
        if(!strlen($route)){
            return $route;
        }
        $route = trim($route,'/');
        $arr = explode('/',$route);
        if(count($arr) != 3){
            return false;
        }
        $module = $arr[0]; //模块
        $controller = $arr[1]; //控制器
        $action = $arr[2]; //方法
        //多级控制器 第一层和最后一层为大驼峰 中间其他层级则为小驼峰
        $controllerArr = explode('.',$controller);
        $lastIndex = count($controllerArr) - 1;
        foreach ($controllerArr as $key => $item){
            if(strlen($item)){
                $hump = StringHelper::convertUnderline($item);
                if($key == 0 || $key == $lastIndex){
                    $controllerArr[$key] = ucfirst($hump);
                }else{
                    $controllerArr[$key] = $hump;
                }
            }
        }
        $controller = implode('.',$controllerArr);
        //方法要转换为小驼峰
        $action = StringHelper::convertUnderline($action);
        return $module . '/' .$controller . '/' .$action;
    }

    /**
     * 根据是否为菜单,进行删减字段
     * @param array $param 表单数据
    */
    public function checkIsMenuField($param){
        $isMenu = $param['is_menu'];
        if(!$isMenu){
            //非菜单
            $param['icon'] = '';
            $param['redirect_url'] = '';
            $param['menu_name'] = '';
        }
        return $param;
    }

    /**
     * 添加后台权限
     * @param array $param 表单数据
     */
    public function addAuth($param){
        //验证route参数
        $res = $this->checkRouteParam($param['route']);
        if($res === false){
            $this->setError($this->CODE_FAIL,'route参数格式错误');
            return false;
        }
        $param['route'] = $res;
        //修剪字段
        $param = $this->checkIsMenuField($param);

        //获取父级权限信息
        $parentInfo = $this->where([
            'id' => $param['pid'], //父节点
        ])->field('pid,level,path,is_menu,root')->findOrEmpty()->toArray();

        if(!$parentInfo){
            $this->setError($this->CODE_FAIL,'无效的父级权限');
            return false;
        }

        //本权限数据
        $param['route'] = isset($param['route']) ? ltrim($param['route'],'/') : ''; //路由统一不以 / 开头 首字母大写
        $param['level'] = $parentInfo['level'] + 1; //层级 = 父权限层级 + 1
        $param['sorts'] = isset($param['sorts']) ? $param['sorts'] : 0;
        $param['create_time'] = time();

        //保存
        $this->startTrans();
        try{
            //第一次保存本权限
            $id = $this->field('pid,name,route,redirect_url,menu_name,sorts,icon,is_menu,level,create_time')->insertGetId($param);
            if(!$id){
                $this->rollback();
                $this->setError($this->CODE_FAIL,'添加失败');
                return false;
            }
            //第二次保存本权限,修正path,root等字段
            $update = [
                'root' => $parentInfo['pid'] == 0 ? $id : $parentInfo['root'], //如果所选父权限不是顶级权限,则本权限root = 父权限root，如果父权限是顶级权限,则本权限root = 本权限id
                'path' => $parentInfo['path'] . ',' .$id, //path = 父节点path . ',' . 本节点id
            ];
            if (!$this->save($update,['id' => $id])) {
                $this->rollback();
                $this->setError($this->CODE_FAIL,'添加失败');
                return false;
            }

            $this->commit();
            return true;
        }catch (Exception $e){
            $this->rollback();
           return false;
        }
    }


    /**
     * 根据id查询单条权限数据
     * @param int   $id    权限id
     */
    public function getAuthById($id){
        $info = $this->alias('a')
            ->join('server_permission p','a.pid=p.id','left')
            ->field('a.*,p.name as parent_name')
            ->where([
                'a.id' => $id,
            ])->findOrEmpty()->toArray();
        return $info;
    }

    /**
     * 修改权限
     * @param array $param 表单数据
     */
    public function modifyAuth($param){
        //验证route参数
        $res = $this->checkRouteParam($param['route']);
        if($res === false){
            $this->setError($this->CODE_FAIL,'route参数格式错误');
            return false;
        }
        $param['route'] = $res;

        //修剪字段
        $param = $this->checkIsMenuField($param);

        //获取本权限信息
        $selfInfo = $this->getAuthById($param['id']);
        if(!$selfInfo){
            $this->setError($this->CODE_FAIL,'数据不存在');
            return false;
        }

        //获取修改后指定的父级权限信息
        $parentInfo = $this->where([
            'id' => $param['pid'], //父节点
        ])->field('id,name,pid,level,path,is_menu,root')->findOrEmpty()->toArray();
        if(!$parentInfo){
            $this->setError($this->CODE_FAIL,'无效的父级权限');
            return false;
        }

        //新的父节点不能是其本身,也不能是其子节点
        if($param['pid'] == $param['id']){
            $this->setError($this->CODE_FAIL,'父级权限不能是其本身');
            return false;
        }

        //其子节点
        $son = $this->getSonByPath($selfInfo['path']);
        if($son){
            $sonIdArr = array_column($son,'id');
            if(in_array($param['pid'],$sonIdArr)){
                $this->setError($this->CODE_FAIL,'父级权限不能是其子节点');
                return false;
            }
        }

        //如果所选父权限为顶级权限,则root = 其id本身,否则root = 其父权限root
        $root = $parentInfo['pid'] == 0 ? $param['id'] : $parentInfo['root'];

        //本权限数据
        $param['route'] = isset($param['route']) ? ltrim($param['route'],'/') : ''; //路由统一不以 / 开头
        $param['level'] = $parentInfo['level'] + 1; //层级 = 父权限层级 + 1
        $param['path'] =  $parentInfo['path'].','.$param['id']; //层级path = 父path  .','. 本节点id
        $param['root'] = $root; //一级权限id
        $param['sorts'] = isset($param['sorts'] )? $param['sorts'] : 0;
        $param['update_time'] = time();

        $this->startTrans();
        try{
            //保存本权限数据
            if (!$this->allowField('pid,name,is_menu,route,sorts,icon,level,path,root,redirect_url,menu_name,update_time')->save($param,['id' => $param['id']])) {
                $this->rollback();
                $this->setError($this->CODE_FAIL,'修改失败');
                return false;
            }

            //移动了本权限节点,将其子权限一并移动
            if(($param['pid'] != $selfInfo['pid']) && $son) {
                if (!$this->moveByInfo($son,$selfInfo,$parentInfo,false)) {
                    $this->rollback();
                    $this->setError($this->CODE_FAIL,'修改失败');
                    return false;
                }
            }

            $this->commit();
            return true;
        }catch (Exception $e){
            $this->rollback();
            $this->setError($this->CODE_FAIL,'修改失败');
            return false;
        }

    }

    /**
     * 根据节点path查询其所有子节点
     * @param string $path 父节点path
     * @param array  $where 附加查询条件
     * @param string $field 查询字段
     */
    public function getSonByPath($path,$where = array(),$field = '*'){
        //path添加逗号 ,
        $path .= ',';
        $sons = $this->where("locate('{$path}',`path`)") //子节点 ： path包含该父节点的path
        ->where($where)->field($field)->select()->toArray();
        return $sons;
    }

    /**
     * 已知子权限数据,移动权限节点
     * @param array $sonInfo 要移动的子权限数据
     * @param array $selfInfo 本节点老数据
     * @param array $parentInfo 最新父节点数据
     * @param bool  $moveSelf 是否移动本节点
     */
    public function moveByInfo($sonInfo,$selfInfo,$parentInfo,$moveSelf = true){
        //参数过滤
        if(!$sonInfo || !$selfInfo || !$parentInfo){
            return false;
        }

        //所属一级权限id
        $root = $parentInfo['pid'] == 0 ? $selfInfo['id'] : $parentInfo['root'];
        $sons = []; //要移动的子节点数据
        //移动本节点
        $selfInfo['old_level'] = $selfInfo['level']; //老level
        $selfInfo['pid'] = $parentInfo['id'];
        $selfInfo['level'] = $parentInfo['level'] + 1;
        $selfInfo['path'] = $parentInfo['path'].','.$selfInfo['id']; //移动后的本节点数据
        if($moveSelf)
            $sonInfo[] = $selfInfo;
        //循环处理子节点新数据
        foreach ($sonInfo as $key => $item){
            if($item['id'] == $selfInfo['id']){
                //移动本节点
                if($moveSelf){
                    $sons[] = [
                        'id' => $item['id'],
                        'pid' => $selfInfo['pid'],
                        'level' => $selfInfo['level'],
                        'path' => $selfInfo['path'],
                        'root' => $root,
                    ];

                }else{
                    continue;
                }

            }else{
                //移动子节点
                //分析子节点路径,获取从本身节点开始之后的path字符串
                $pathArr = explode(',',$item['path']);
                //本节点key
                $index = array_search($selfInfo['id'],$pathArr);
                if($index === false)
                    return false;
                $index += 1; //不包括本身节点
                $childStr = implode(',',array_slice($pathArr,$index));
                if(!$childStr)
                    return false;

                //移动子节点
                $sons[] = [
                    'id' => $item['id'],
                    'pid' => $item['pid'], //pid不变
                    'level' => $selfInfo['level'] + $item['level'] - $selfInfo['old_level'],//新level = 本身节点level + 该子节点的原等级 - 自身节点的老等级
                    'path' => $selfInfo['path'] . ',' . $childStr, //新path = 本身节点path + ',' + 从本身节点开始之后的原节点路径的截取
                    'root' => $root
                ];

            }

        }

        //保存数据
        try{
            $this->isUpdate(true)->allowField(['pid','level','path','root'])->saveAll($sons);
            return true;
        }catch (Exception $e){
            return false;
        }

    }

    /**
     * 删除后台权限
     * 1.删除同时,一起删除其所有子权限数据
     * 2.需要删除相关角色和权限的对应关系
     * @param int $id 权限id
     */
    public function del($id){
        $idStr = $id;
        $info = $this->where([
            ['pid','neq',0], //非根节点
            ['id','eq',$id],
        ])->findOrEmpty()->toArray();
        if(!$info){
            $this->setError($this->CODE_FAIL,'数据不存在');
            return false;
        }

        //找到所有的子节点
        $sons = $this->getSonByPath($info['path']);
        if($sons){
            $sonId = implode(',',array_column($sons,'id'));
            $idStr = $idStr . ',' . $sonId;
        }

        $this->startTrans();
        try{
            //同时本权限和下级权限
            if (!$this->where('id','in',$idStr)->delete()) {
                $this->rollback();
                $this->setError($this->CODE_FAIL,'删除失败');
                return false;
            }

            //删除角色权限中间表数据
            \model('ServerGroupAuthMapping')->where([
                ['auth_id','in',$idStr]
            ])->delete();

            $this->commit();
            return true;
        }catch (Exception $e){
            $this->rollback();
            $this->setError($this->CODE_FAIL,'删除失败');
            return false;
        }
    }


    /**
     * 查询某系统全部权限数据
     * @param bool $isTree  是否整理树形结构
     */
    public function getAllAuth($isTree = false){
        if(!$isTree){
            $order = ['sorts' => 'asc','path' => 'asc'];
        }else{
            $order = ['sorts' => 'asc'];
        }
        $list = $this->order($order)->select()->toArray();
        if(!$list || !$isTree){
            return $list;
        }
        $list = Algorithm::tree($list,0,1,'children');
        return $list;
    }

    /**
     * 根据权限ID字符串,查询权限数据
     * @param string $idStr  权限id字符串
     * @param string $field  查询字段
     * @param array  $where  附加查询条件
     */
    public function getAuthByIdStr($idStr,$field = '*',$where = []){
        if(!$idStr){
            throw new Exception('参数错误');
        }
        return $this->where([
            ['id','in',$idStr]
        ])->where($where)->field($field)->order('path','asc')->select()->toArray();
    }

    /**
     * 根据权限数据组装菜单数据
     * @param array $auths  已经根据排序值字段排序好的权限数组
     * @param bool  $toJson 是否转换为json格式
     */
    public function getMenu($auths,$toJson = false){
        $auths = $auths ? $auths : [];
        //转换字段
        $menu = [];
        foreach ($auths as $key => $item){
            //必须是菜单
            if($item['is_menu'] || !$item['pid']){
                $menu[$key] = [
                    'id' => $item['auth_id'],
                    'pid' => $item['pid'],
                    //转换的字段
                    'name' => $item['menu_name'],
                    'path' => $item['redirect_url'],
                    'text' => $item['name'],
                ];
                if(isset($item['icon']) && $item['icon']){
                    $menu[$key]['type'] = $item['icon'];
                }
            }
        }

      if(!$menu)
          return $menu;
      //整理成树形结构
      $tree = Algorithm::tree($menu,1,1,'children',true);
      if(!$tree)
          return $tree;
      $tree = Algorithm::delTreeKey($tree,['id','pid']);
      if($toJson){
          $tree = json_encode($tree,JSON_UNESCAPED_UNICODE);
      }
      return $tree;
    }
}